﻿/**
 * The NumericStepper component displays a single number in the range assigned to it, and supports the ability to increment and decrement the value based on an arbitrary step size.
 
	<b>Inspectable Properties</b>
	A MovieClip that derives from the NumericStepper component will have the following inspectable properties:<ul>
	<li><i>visible</i>: Hides the component if set to false.</li>
	<li><i>disabled</i>: Disables the component if set to true.</li>
	<li><i>value</i>: The numeric value displayed by the NumericStepper.</li>
	<li><i>minimum</i>: The minimum value of the NumericStepper’s range.</li> 
	<li><i>maximum</i>: The maximum value of the NumericStepper’s range.</li>
	<li><i>enableInitCallback</i>: If set to true, _global.CLIK_loadCallback() will be fired when a component is loaded and _global.CLIK_unloadCallback will be called when the component is unloaded. These methods receive the instance name, target path, and a reference the component as parameters.  _global.CLIK_loadCallback and _global.CLIK_unloadCallback should be overriden from the game engine using GFx FunctionObjects.</li>
	<li><i>soundMap</i>: Mapping between events and sound process. When an event is fired, the associated sound process will be fired via _global.gfxProcessSound, which should be overriden from the game engine using GFx FunctionObjects.</li></ul>
	 
	<b>States</b>
	The NumericStepper component supports three states based on its focused and disabled properties. <ul>
	<li>default or enabled state.</li>
	<li>focused state, that highlights the textField area.</li>
	<li>disabled state.</li></ul>
	
	<b>Events</b>
	All event callbacks receive a single Object parameter that contains relevant information about the event. The following properties are common to all events. <ul>
	<li><i>type</i>: The event type.</li>
	<li><i>target</i>: The target that generated the event.</li></ul>
		
	The events generated by the NumericStepper component are listed below. The properties listed next to the event are provided in addition to the common properties.<ul>
	<li><i>show</i>: The component's visible property has been set to true at runtime.</li>
	<li><i>hide</i>: The component's visible property has been set to false at runtime.</li>
	<li><i>change</i>: The NumericStepper’s value has changed.</li>
	<li><i>stateChange</i>: The NumericStepper’s focused or disabled property has changed.<ul>
		<li><i>state</i>: Name of the new state. String type. Values "default", "focused" or "disabled".</li></ul></li></ul>
 */

/**************************************************************************

Filename    :   NumericStepper.as

Copyright   :   Copyright 2011 Autodesk, Inc. All Rights reserved.

Use of this software is subject to the terms of the Autodesk license
agreement provided at the time of installation or download, or which
otherwise accompanies this software in either electronic or hard copy form.

**************************************************************************/

/*
*/

import flash.external.ExternalInterface; 
import gfx.core.UIComponent;
import gfx.controls.Button;
import gfx.ui.InputDetails;
import gfx.ui.NavigationCode;
import gfx.utils.Constraints;

[InspectableList("disabled", "visible", "maximum", "minimum", "value", "enableInitCallback", "soundMap")]
class gfx.controls.NumericStepper extends UIComponent {
	
// Constants

// Public Properties
	/** The amount the value is incremented or decremented. */
	[Inspectable(defaultValue="1")]
	public var stepSize:Number = 1;
	/** Mapping between events and sound process **/
	[Inspectable(type="Object", defaultValue="theme:default,focusIn:focusIn,focusOut:focusOut,change:change")]
	public var soundMap:Object = { theme:"default", focusIn:"focusIn", focusOut:"focusOut", change:"change" };

// Private Properties
	private var _labelFunction:Function;
	private var _maximum:Number = 10;
	private var _minimum:Number = 0;
	private var _stepSize:Number;
	private var _value:Number = 0;
	private var constraints:Constraints;
	
// UI Elements
	/** An optional Button used to increment the {@code value}. */
	public var nextBtn:Button;
	/** An optional Button used to decrement the {@code value}. */
	public var prevBtn:Button;
	/** The TextField label used to display the {@code value}. Note that when state changes are made, the textField instance may change, so changes made to it externally may be lost. */
	public var textField:TextField;
	
	
// Initialization
	/**
	 * The constructor is called when a NumericStepper or a sub-class of NumericStepper is instantiated on stage or by using {@code attachMovie()} in ActionScript. This component can <b>not</b> be instantiated using {@code new} syntax. When creating new components that extend NumericStepper, ensure that a {@code super()} call is made first in the constructor.
	 */
	public function NumericStepper() { 
		super();		
		tabChildren = false;
		focusEnabled = tabEnabled = !_disabled;	
	}
	
	
// Public Methods	
	/**
	 * The maximum allowed value. The {@code value} property will always be less than or equal to the {@code maximum}. 
	 */
	[Inspectable(defaultValue="10")]
	public function get maximum():Number { return _maximum; }
	public function set maximum(value:Number):Void {
		_maximum = value;
		value = _value;
	}	
	
	/**
	 * The minimum allowed value. The {@code value} property will always be greater than or equal to the {@code minimum}. 
	 */
	[Inspectable(defaultValue="0")]
	public function get minimum():Number { return _minimum; }
	public function set minimum(value:Number):Void {
		_minimum = value;
		value = _value;
	}
	
	/**
	 * The value of the numeric stepper. The {@code value} property will always be kept between the {@code mimimum} and {@code maximum}.
	 * @see #minimum
	 * @see #maximum
	 */
	[Inspectable(name="value", defaultValue="0")]
	public function get value():Number { return _value; }
	public function set value(v:Number):Void {
		v = lockValue(v);
		if (v == _value) { return; }
		_value = v;
		if (initialized) { dispatchEventAndSound({type:"change"}); }
		invalidate();
	}

	/**
	 * Disable this component. Focus (along with keyboard events) and mouse events will be suppressed if disabled.
	 */	
	[Inspectable(defaultValue="false", verbose="1")]
	public function get disabled():Boolean { return _disabled; }
	public function set disabled(value:Boolean):Void {
		if (_disabled == value) { return; }
		super.disabled = value;
		focusEnabled = tabEnabled = !_disabled;
		gotoAndPlay(_disabled ? "disabled" : (_focused ? "focused" : "default"));
		if (!initialized) { return; }
		updateAfterStateChange();
		prevBtn.disabled = nextBtn.disabled = _disabled;	
	}		
	
	/**
	 * The function used to determine the label.
	 */
	public function get labelFunction():Function { return _labelFunction; }
	public function set labelFunction(value:Function):Void {
		_labelFunction = value;
		updateLabel();
	}
	
	/**
	 * Increment the {@code value} of the NumericStepper, using the {@code stepSize}.
	 */
	public function increment():Void { onNext(null); }
	
	/**
	 * Decrement the {@code value} of the NumericStepper, using the {@code stepSize}.
	 */
	public function decrement():Void { onPrev(null); }
	
	public function handleInput(details:InputDetails, pathToFocus:Array):Boolean {
		var keyPress:Boolean =(details.value == "keyDown" || details.value == "keyHold");
		switch (details.navEquivalent) {
			case NavigationCode.RIGHT:
				if (_value < _maximum) {
					if (keyPress) {
						onNext(null);
					}
					return true;
				}
				break;
			case NavigationCode.LEFT:
				if (_value > _minimum) {
					if (keyPress) {
						onPrev(null);
					}
					return true;
				}
				break;
				
			case NavigationCode.HOME:
				if (!keyPress) {
					value = _minimum;
				}
				return true;
			case NavigationCode.END:
				if (!keyPress) {
					value = _maximum;					
				}
				return true;
		}
		return false;
	}
	
	/** @exclude */
	public function toString():String {
		return "[ Scaleform NumericStepper: " + _name + "]";
	}
	
// Private Methods
	private function configUI():Void {					
		nextBtn.addEventListener("click", this ,"onNext");
		prevBtn.addEventListener("click", this, "onPrev");
		nextBtn.focusTarget = prevBtn.focusTarget = this;
		nextBtn.tabEnabled = prevBtn.tabEnabled = false;
		nextBtn.autoRepeat = prevBtn.autoRepeat = true;
		prevBtn.disabled = nextBtn.disabled = _disabled;	
		
		constraints = new Constraints(this,true);
		constraints.addElement(textField, Constraints.LEFT | Constraints.RIGHT);
		super.configUI();		
	}
	
	private function draw():Void {
		if (sizeIsInvalid) { 
			_width = __width;
			_height = __height;
		}
		if (constraints != null) { constraints.update(__width, __height); }
		updateLabel();
	}
	
	private function changeFocus():Void {
		gotoAndPlay(_disabled ? "disabled" : _focused ? "focused" : "default");
		updateAfterStateChange();
		prevBtn.displayFocus = nextBtn.displayFocus = (_focused != 0);
	}
	
	private function updateAfterStateChange():Void {
		if (constraints != null) { constraints.update(__width, __height); }
		updateLabel();
		dispatchEvent({type:"stateChange", state:_disabled ? "disabled" : _focused ? "focused" : "default"});
	}
	
	private function onNext(evtObj:Object):Void {
		value = _value + stepSize;
	}
	
	private function onPrev(evtObj:Object):Void {
		value = _value - stepSize;
	}
	
	// Lock the value to the step size and min/max
	private function lockValue(value:Number):Number {
		var newVal:Number = Math.max(_minimum, Math.min(_maximum, stepSize * Math.round(value / stepSize)));
		return newVal;
	}
	
	private function updateLabel():Void {
		var label:String = _value.toString();
		if (_labelFunction != null) { 
			label = _labelFunction(_value);
		}
		textField.text = label;
	}		

}